using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Collections;
using System.Xml;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Threading;
using CodePlex.Resourceful.Silverlight;
using System.Text.RegularExpressions;

namespace CodePlex.Resourceful
{
   
    public static class Utils
    {

        // async

        public static TResult MakeSynchronous<TResult>(Action20<Action<TResult>> fn)
        {
            TResult rt = default(TResult);
            ManualResetEvent reset = new ManualResetEvent(false);

            fn(delegate(TResult t)
            {
                rt = t;
                reset.Set();
            });

            reset.WaitOne();
            return rt;
        }

        public static TResult MakeSynchronous<T1, TResult>(T1 arg1, Action20<T1, Action<TResult>> fn )
        {
            return MakeSynchronous(Partial(fn, arg1));
        }
        public static TResult MakeSynchronous<T1, T2, TResult>(T1 arg1, T2 arg2, Action20<T1, T2, Action<TResult>> fn)
        {
            return MakeSynchronous(Partial(fn, arg1, arg2));
        }
        public static TResult MakeSynchronous<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3, Action20<T1, T2, T3, Action<TResult>> fn)
        {
            return MakeSynchronous(Partial(fn, arg1, arg2, arg3));
        }

        public static T MakeSynchronous<T>(Func20<AsyncCallback, object, IAsyncResult> begin, Func20<IAsyncResult, T> end)
        {

            // TODO use internal
            T rt = default(T);
            ManualResetEvent reset = new ManualResetEvent(false);

            AsyncCallback cb = delegate(IAsyncResult item)
            {
                rt = end(item);
                reset.Set();
            };
            begin(cb, null);

            reset.WaitOne();
            return rt;
        }



        // http

        public static string EncodeUrlPath(string path, NameValuePairs parameters)
        {
            parameters = parameters ?? new NameValuePairs();

            StringBuilder rt = new StringBuilder();
            rt.Append(path);
            if (parameters.Count > 0)
            {
                rt.Append("?" + Utils.EncodeUrlPath(parameters));
            }
            return rt.ToString();
        }

        public static string EncodeUrlPath(NameValuePairs parameters)
        {
            IEnumerable<string> nv = Fn.Select<string, string>(parameters.Keys, delegate(string item)
            {
               return Utils.UrlEncode(item) + "=" + Utils.UrlEncode(parameters[item]);
            });

            return Fn.Join("&", nv);
        }

        public static string GetPathAndQuery(Uri uri)
        {
            return uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped);
        }



        // reflection 

        public static void SetInstanceField(object target, string fieldname, object value)
        {
            LocateField(target, fieldname).SetValue(target, value);
        }

        private static FieldInfo LocateField(object target, string fieldname)
        {
            Type t = target.GetType();

            FieldInfo fi = null;
            do
            {
                fi = t.GetField(fieldname, BindingFlags.GetField | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
                t = t.BaseType;
            } while (fi == null && t != null);

            if (fi == null)
            {
                throw new ArgumentException("Field '" + fieldname + "' not found.");
            }
            return fi;
        }

        public static object GetInstanceField(object target, string fieldname)
        {
            return LocateField(target,fieldname).GetValue(target);
        }


  
        // collections

        public static bool In(object target, params object[] values)
        {
            return Array.IndexOf(values, target) > -1;
        }
        public static T[] Subarray<T>(T[] values, int start)
        {
            return Subarray<T>(values, start, values.Length - start);
        }
        public static T[] Subarray<T>(T[] values, int start, int length)
        {
            T[] rt = new T[length];
            Array.Copy(values, start, rt, 0, length);
            return rt;
        }








        // dates

        public static string ToHttpDate(DateTime value)
        {
            // http://www.ietf.org/rfc/rfc1123.txt
            // Sun, 06 Nov 1994 08:49:37 GMT
            return value.ToUniversalTime().ToString("r");
        }


        public static string Iso8601Date(DateTime value)
        {
            return value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
        }


        public static DateTime TruncateToSeconds(DateTime value)
        {
            return new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Kind);
        }


        public static string ToAtomDate(DateTime value)
        {
            // http://www.ietf.org/rfc/rfc3339.txt
            // 1985-04-12T23:20:50.52Z

            return value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ");
        }


        public static DateTime FromHttpDate(string value)
        {
            return DateTime.Parse(value).ToLocalTime();
        }








        // strings 

        public static bool IsWhiteSpace(string value)
        {
            foreach (char c in value)
            {
                if (!char.IsWhiteSpace(c))
                {
                    return false;
                }
            }
            return true;
        }

        public static Stream ToStream(string value)
        {
            MemoryStream rt = new MemoryStream(Encoding.UTF8.GetBytes(value));
            rt.Position = 0;
            return rt;
        }

        public static bool ContainsUnprintableUsAsciiChars(string value)
        {
            if (value == null)
            {
                return false;
            }

            for (int i = 0; i < value.Length; i++)
            {
                if ((value[i] < ' ') || (value[i] > '~'))
                {
                    return true;
                }
            }
            return false;
        }

        public static string SplitPascalIntoWords(string pascalCased)
        {

            StringBuilder rt = new StringBuilder();

            for (int i = 0; i < pascalCased.Length; i++)
            {
                char c = pascalCased[i];

                // leading upper
                if (char.IsUpper(c) && i > 0 && char.IsLower(pascalCased[i - 1]))
                {
                    rt.Append(' ');
                }

                // trailing upper
                if (char.IsLower(c) && i > 1 && char.IsUpper(pascalCased[i - 1]) && char.IsUpper(pascalCased[i - 2]))
                {
                    rt.Insert(rt.Length - 1, ' ');
                }

                // leading digit
                if (char.IsDigit(c) && i > 0 && !char.IsDigit(pascalCased[i - 1]))
                {
                    rt.Append(' ');
                }

                // trailing digit
                if (char.IsUpper(c) && i > 1 && char.IsDigit(pascalCased[i - 1]) && char.IsLower(pascalCased[i - 2]))
                {
                    rt.Insert(rt.Length, ' ');
                }

                rt.Append(c);

            }

            return rt.ToString();
        }






        // misc

        public static void Prompt()
        {
            Prompt("Press enter to continue...");
        }

        public static void Prompt(string message)
        {
            Console.WriteLine(message);
            Console.ReadLine(); // silverlight does not have ReadKey
        }

        public static T ChangeType<T>(object value)
        {
            return (T)Convert.ChangeType(value, typeof(T), Thread.CurrentThread.CurrentCulture);
        }

        public static string ToHexXmlCharacterEntity(char c)
        {
            return "&#x" + ((int)c).ToString("x") + ";";
        }







        // encryption/hashing

#if SILVERLIGHT

        // SILVERLIGHT-ONLY!

         public static byte[] ToHMACSHA1(Encoding encoding, string key, string input)
        {
            return SilverlightCompat.ToHMACSHA1(encoding, key, input);
        }
         public static byte[] ToMD5(byte[] value)
         {
             return SilverlightCompat.ToMD5(value);
         }


#else
        // NON-SILVERLIGHT-ONLY!

        public static byte[] ToHMACSHA1(Encoding encoding, string key, string input)
        {
            HMACSHA1 signature = new HMACSHA1(encoding.GetBytes(key));
            return signature.ComputeHash(encoding.GetBytes(input.ToCharArray()));
        }

        public static byte[] ToHMACSHA256(Encoding encoding, byte[] key, string input)
        {
            HMACSHA256 signature = new HMACSHA256(key);
            return signature.ComputeHash(encoding.GetBytes(input));
        }

        public static Assembly LoadWithPartialName(string partialName)
        {
#pragma warning disable 618
            return Assembly.LoadWithPartialName(partialName);
#pragma warning restore 618
        }


        public static byte[] ToMD5(string value, Encoding encoding)
        {
            return Utils.ToMD5(encoding.GetBytes(value));
        }

        public static byte[] ToMD5(byte[] value)
        {
            using (MD5CryptoServiceProvider x = new MD5CryptoServiceProvider())
            {
                return x.ComputeHash(value);
            }
        }

        public static byte[] ToMD5(Stream stream)
        {
            using (MD5CryptoServiceProvider x = new MD5CryptoServiceProvider())
            {
                return x.ComputeHash(stream);
            }
        }








        // xml

        public static XmlNamespaceManager GetNamespaceManager(XmlDocument doc, string defaultPrefix)
        {

            XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);

            foreach (XmlAttribute attribute in doc.DocumentElement.Attributes)
            {
                if (attribute.Name == "xmlns")
                {
                    ns.AddNamespace(defaultPrefix, attribute.Value);
                }
                else
                {
                    if (attribute.Prefix == "xmlns")
                    {
                        ns.AddNamespace(attribute.LocalName, attribute.Value);
                    }
                   
                }
            }

            return ns;

        }


        public static IEnumerable<T> GetConstants<T>(Type type)
        {
            Type t = typeof(T);
            foreach (FieldInfo fi in type.GetFields(BindingFlags.Static | BindingFlags.Public))
            {
                if (t.IsAssignableFrom(fi.FieldType))
                {
                    yield return Utils.ChangeType<T>(fi.GetValue(null));
                }
            }

        }

        public static void TraceWriteLine(string message, params object[] args)
        {
            Trace.WriteLine(string.Format(message, args));
        }

#endif





        // encoding 

        public static string ToBase16(params byte[] bytes)
        {
            StringBuilder rt = new StringBuilder();
            foreach (byte b in bytes)
            {
                rt.Append(b.ToString("x2"));
            }
           return rt.ToString();
        }

        public static byte[] FromBase16(string value)
        {
            byte[] rt = new byte[value.Length / 2];
            for (int i = 0; i < value.Length; i += 2)
            {
                rt[i/2] = byte.Parse(value.Substring(i,2), NumberStyles.HexNumber);
            }
            return rt;
        }

        public static string ToBase64(byte[] bytes)
        {
            return Convert.ToBase64String(bytes);
        }

        public static byte[] FromBase64(string value)
        {
            return Convert.FromBase64String(value);
        }













        // io


        public static long GetStreamLength(Stream stream)
        {
            if (stream == null)
                return 0;

            long rt = default(long);

            BufferedTransfer(stream, Stream.Null, delegate(long item)
            {
                rt = item;
            });

            return rt;
        }


        public static void BufferedTransfer(Stream input, Stream output)
        {
            Utils.BufferedTransfer(input, output, null);
        }
        public static void BufferedTransfer(Stream input, Stream output, Action<long> bytesTransferred)
        {
            int bufferSize = 4096;

#if SILVERLIGHT
            Stream inputStream = input;
#else
            Stream inputStream =  new BufferedStream(input, bufferSize);
#endif
            
            byte[] bytes = new byte[bufferSize];

            long totalBytes = 0;
            int read = 0;
            while ((read = inputStream.Read(bytes, 0, bytes.Length)) != 0)
            {
                output.Write(bytes, 0, read);

                totalBytes += read;
                if (bytesTransferred != null)
                {
                    bytesTransferred(totalBytes);
                }

            }

            output.Flush();
        }



        public static string GetGroup(string input, string pattern)
        {
            return Regex.Match(input, pattern).Groups[1].Value;
        }


#if SILVERLIGHT

        
        public static bool CheckHostName(string hostName)
        {
            return true; // TODO
        }


        public static string UrlEncode(string value)
        {
            return System.Windows.Browser.HttpUtility.UrlEncode(value);
        }

#else


        public static bool CheckHostName(string hostName)
        {
            return Uri.CheckHostName(hostName) == UriHostNameType.Dns;
        }

        public static string UrlEncode(string value) {

            return System.Web.HttpUtility.UrlEncode(value);
        }

        public static string UrlDecode(string value)
        {
            return System.Web.HttpUtility.UrlDecode(value);
        }
         public static void PostProcess(string response, IHttpContext context, DateTime lastModified)
        {
            Utils.PostProcess(Encoding.UTF8.GetBytes(response), context, lastModified);
        }


         public static void PostProcess(byte[] responseBuffer, IHttpContext context, DateTime lastModified)
        {

            byte[] buffer = responseBuffer;
            byte[] md5 = Utils.ToMD5(buffer);
            string etag = Utils.ToBase16(md5);


            context.Response.ContentLength = buffer.Length;

            context.Response.SetHeader(HttpHeader.LastModified, Utils.ToHttpDate(lastModified));
            context.Response.SetHeader(HttpHeader.ETag, etag);


            if (!context.Request.HttpMethod.Equals(HttpMethod.Head, StringComparison.InvariantCultureIgnoreCase))
            {
                using(Stream output =  context.Response.GetOutputStream())
                {
                    Utils.BufferedTransfer(new MemoryStream(buffer), output);
                }
            }

            
            context.Response.End();
            
            
        }




        // more xml

        public static IDisposable StartElement(XmlWriter writer, string name)
        {
            writer.WriteStartElement(name);

            return new QuickDisposable(writer.WriteEndElement);
        }


        public static string ToString(Action<XmlTextWriter> writer)
        {
            using (MemoryStream rt = new MemoryStream())
            {

                XmlTextWriter xwriter = new XmlTextWriter(rt, Encoding.UTF8);
                xwriter.Formatting = Formatting.Indented;
                xwriter.WriteStartDocument();
                writer(xwriter);
                xwriter.Flush();
                rt.Position = 0;
                return new StreamReader(rt).ReadToEnd();
            }
        }

        public static void PrettyPrint(XmlDocument doc, TextWriter writer)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            XmlWriter w = XmlWriter.Create(writer, settings);
            doc.WriteContentTo(w);
            w.Flush();
        }


#endif


















        // generics

        public static Action20<T1> Partial<T1, T2>(Action20<T1, T2> fn, T2 arg2)
        {
            return delegate(T1 t1)
            {
                fn(t1, arg2);
            };
        }
        public static Action20<T1> Partial<T1, T2, T3>(Action20<T1, T2, T3> fn, T2 arg2, T3 arg3)
        {
            return delegate(T1 t1)
            {
                fn(t1, arg2, arg3);
            };
        }
        public static Action20<T1> Partial<T1, T2, T3, T4>(Action20<T1, T2, T3, T4> fn, T2 arg2, T3 arg3, T4 arg4)
        {
            return delegate(T1 t1)
            {
                fn(t1, arg2, arg3, arg4);
            };
        }



        public static Action20<T2> Partial<T1, T2>(Action20<T1, T2> fn, T1 arg1)
        {
            return delegate(T2 t2)
            {
                fn(arg1, t2);
            };
        }
        public static Action20<T3> Partial<T1, T2, T3>(Action20<T1, T2, T3> fn, T1 arg1, T2 arg2)
        {
            return delegate(T3 t3)
            {
                fn(arg1, arg2, t3);
            };
        }
        public static Action20<T4> Partial<T1, T2, T3, T4>(Action20<T1, T2, T3, T4> fn, T1 arg1, T2 arg2, T3 arg3)
        {
            return delegate(T4 t4)
            {
                fn(arg1, arg2, arg3, t4);
            };
        }

        public static V SafeGet<K, V>(Dictionary<K, V> dict, K key)
        {
            return dict.ContainsKey(key) ? dict[key] : default(V);
        }


        // compat

        public static string ReadString(XmlReader reader)
        {
            return SilverlightCompat.ReadString(reader);
        }







        // Size In Bytes <-> String

        public static string ComputeSizeString(long bytes)
        {
            if (bytes < 1024)
            {
                return bytes + " bytes";
            }

            if (bytes < 1024 * 1024)
            {
                return string.Format("{0:.##} KB", ((double)bytes) / ((double)(1024)));
            }

            if (bytes < 1024 * 1024 * 1024)
            {
                return string.Format("{0:.##} MB", ((double)bytes) / ((double)(1024 * 1024)));
            }

            return string.Format("{0:.##} GB", ((double)bytes) / ((double)(1024 * 1024 * 1024)));


        }


        public static long ParseSizeString(string size)
        {
            string[] tokens = size.Split(' ');

            if (tokens[1] == "bytes")
            {
                return long.Parse(tokens[0]);
            }
            if (tokens[1] == "KB")
            {
                return (long)(double.Parse(tokens[0]) * 1024D);
            }
            if (tokens[1] == "MB")
            {
                return (long)(double.Parse(tokens[0]) * 1024D * 1024D);
            }
            if (tokens[1] == "GB")
            {
                return (long)(double.Parse(tokens[0]) * 1024D * 1024D * 1024D);
            }

            throw CodePlexException.Format("Cannot parse size string '{0}'.", size);

        }




       
    }
}
